package com.nervenets.web.rabbitmq;

import com.nervenets.utils.NumberUtil;
import com.nervenets.web.controller.Global;
import com.nervenets.web.enumeration.*;
import com.nervenets.web.hibernate.dao.UserDao;
import com.nervenets.web.hibernate.domain.film.Movie;
import com.nervenets.web.hibernate.domain.user.Order;
import com.nervenets.web.hibernate.domain.user.User;
import com.nervenets.web.hibernate.domain.user.WealthFlow;
import com.nervenets.web.redis.GlobalService;
import com.nervenets.web.redis.GlobalVariablesService;
import com.nervenets.web.redis.domain.global.GlobalRule;
import com.nervenets.web.redis.domain.movie.MovieStored;
import com.nervenets.web.redis.domain.user.UserStored;
import com.nervenets.web.redis.utils.RedisSyncLocker;
import com.rabbitmq.client.Channel;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.amqp.support.AmqpHeaders;
import org.springframework.messaging.handler.annotation.Header;
import org.springframework.messaging.handler.annotation.Payload;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.math.BigDecimal;
import java.util.HashMap;
import java.util.List;

/**
 * Created by Joe on 2018/1/4.
 */
@Component
@RabbitListener(queues = "flows", containerFactory = "rabbitListenerContainerFactory")
public class FlowsQueueReceiver {
    private static Logger logger = LoggerFactory.getLogger(FlowsQueueReceiver.class);
    @Resource
    RedisSyncLocker redisSyncLocker;
    @Resource
    UserDao userDao;
    @Resource
    GlobalService globalService;
    @Resource
    GlobalVariablesService globalVariablesService;

    @RabbitHandler
    public void process(@Payload FlowMessage flowMessage, @Header(AmqpHeaders.DELIVERY_TAG) long deliveryTag, Channel channel) throws Exception {
        long userId = flowMessage.getUserId();
        long ip = flowMessage.getLongValue("ip");

        FlowType type = flowMessage.getFlowType();
        switch (type) {
            case paid:
                Order order = userDao.findById(flowMessage.getUnionId(), Order.class);
                wealthChange(order.getActualPayMoney(), order.getUserId(), OperateType.recharge, flowMessage.getStringValue("detail"), order.getPayType(), ip, order.getId(), UnionType.order, FlowStatus.success);
                break;
            case wealthChange:
                long offset = flowMessage.getIntValue("offset");
                long unionId = flowMessage.getUnionId();
                OperateType operateType = OperateType.valueOf(flowMessage.getStringValue("operateType"));
                PayType payType = PayType.valueOf(flowMessage.getStringValue("payType"));
                UnionType unionType = UnionType.valueOf(flowMessage.getStringValue("unionType"));
                FlowStatus flowStatus = FlowStatus.valueOf(flowMessage.getStringValue("flowStatus"));
                String detail = flowMessage.getStringValue("detail");
                wealthChange(offset, userId, operateType, detail, payType, ip, unionId, unionType, flowStatus);
                break;
            case mvPaid:
                mvPaidHandler(flowMessage);
                break;
            case scoreChange:
                offset = flowMessage.getIntValue("offset");
                unionId = flowMessage.getUnionId();
                operateType = OperateType.valueOf(flowMessage.getStringValue("operateType"));
                payType = PayType.valueOf(flowMessage.getStringValue("payType"));
                unionType = UnionType.valueOf(flowMessage.getStringValue("unionType"));
                flowStatus = FlowStatus.valueOf(flowMessage.getStringValue("flowStatus"));
                detail = flowMessage.getStringValue("detail");
                scoreChange(offset, userId, operateType, detail, payType, ip, unionId, unionType, flowStatus);
                break;
            case orderTailConfirm:
                order = userDao.findById(flowMessage.getUnionId(), Order.class);
                if (null != order) {
                    orderBonusHandler(order);
                    orderDividends(order);
                }
                break;
            case orderExpiredTailConfirm:
                order = userDao.findById(flowMessage.getUnionId(), Order.class);
                if (null != order) {
                    orderExpiredTailConfirmHandle(order);
                }
                break;
            case movieProfit:
                movieProfitHandler(flowMessage);
                break;
            case rewardChange:
                offset = flowMessage.getIntValue("offset");
                unionId = flowMessage.getUnionId();
                operateType = OperateType.valueOf(flowMessage.getStringValue("operateType"));
                payType = PayType.valueOf(flowMessage.getStringValue("payType"));
                unionType = UnionType.valueOf(flowMessage.getStringValue("unionType"));
                flowStatus = FlowStatus.valueOf(flowMessage.getStringValue("flowStatus"));
                detail = flowMessage.getStringValue("detail");
                rewardChange(offset, userId, operateType, detail, payType, ip, unionId, unionType, flowStatus);
                break;
        }
        channel.basicAck(deliveryTag, false);
    }

    private void movieProfitHandler(FlowMessage flowMessage) throws Exception {
        final Movie movie = userDao.findById(flowMessage.getUnionId(), Movie.class);
        if (null != movie) {
            final BigDecimal profit = movie.getInfo().getProfit();
            if (null != profit) {
                HashMap<String, Object> params = new HashMap<>();
                params.put("orderType", OrderType.movie);
                params.put("unionId", movie.getId());
                params.put("paidStatus", OrderStatus.paid);
                params.put("deliveryStatus", OrderStatus.delivery);

                int start = 0, limit = 10;
                while (true) {
                    List<Order> orders = userDao.getAllObjectsByHql("from Order where orderType = :orderType and unionId = :unionId and (status = :paidStatus or status = :deliveryStatus) order by id ", params, start, limit);

                    for (Order order : orders) {
                        final long totalMoney = order.getExtra().getNumber() * order.getExtra().getPriceOfUnit();
                        order.setStatus(OrderStatus.finish);
                        final long huibao = movie.getInfo().getProfit().multiply(BigDecimal.valueOf(totalMoney / 100)).multiply(BigDecimal.valueOf(100)).longValue();
                        order.getExtra().setProfit(huibao);
                        userDao.saveOrUpdate(order);
                        wealthChange(huibao + totalMoney, order.getUserId(), OperateType.profit,
                                String.format("订单金额%s元，每元收益%s元，投资回报为%s元", NumberUtil.fen2Yuan(totalMoney), movie.getInfo().getProfit(), NumberUtil.fen2Yuan(huibao)),
                                PayType.surplus, flowMessage.getLongValue("ip"), order.getId(), UnionType.order, FlowStatus.success);
                    }

                    if (orders.size() < limit) {
                        break;
                    }
                }
            }
        }
    }

    private void orderExpiredTailConfirmHandle(Order order) throws Exception {
        final long subtract = order.getActualPayMoney() + order.getExtra().getSurplus();
        wealthChange(subtract, order.getUserId(), OperateType.refundTailConfirm, "未确认尾款退款", order.getPayType(), 0, order.getId(), UnionType.order, FlowStatus.success);
    }

    private void mvPaidHandler(FlowMessage flowMessage) throws Exception {
        final Order order = userDao.findById(flowMessage.getUnionId(), Order.class);
        if (null != order) {
            final long subtract = order.getActualPayMoney() + order.getExtra().getSurplus();
            wealthChange(-subtract, order.getUserId(), OperateType.movieBuy, flowMessage.getStringValue("detail"), order.getPayType(), flowMessage.getLongValue("ip"), order.getId(), UnionType.order, FlowStatus.success);

            if (!order.isOfflinePay()) {
                orderBonusHandler(order);
                orderDividends(order);
            }
        }
    }

    /**
     * 处理已购金额
     *
     * @param order
     */
    private void orderBonusHandler(Order order) {
        globalService.addMovieBuyMoney(order.getUnionId(), order.getExtra().getNumber() * order.getExtra().getPriceOfUnit());

        final long movieBuyMoney = globalService.getMovieBuyMoney(order.getUnionId());
        final Movie movie = userDao.findById(order.getUnionId(), Movie.class);
        if (movieBuyMoney >= movie.getTotalOfMoney()) {
            movie.setStatus(MovieStatus.bonus);
            userDao.saveOrUpdate(movie);
        }
    }

    /**
     * 执行订单奖励
     *
     * @param order
     * @throws Exception
     */
    private void orderDividends(Order order) throws Exception {
        final long totalMoney = order.getExtra().getNumber() * order.getExtra().getPriceOfUnit();
        final BigDecimal scorePropOfBuy = globalVariablesService.getGlobalRule().getScorePropOfBuy();
        scoreChange(scorePropOfBuy.multiply(BigDecimal.valueOf(totalMoney)).longValue(), order.getUserId(), OperateType.movieBuy, "积分奖励", order.getPayType(), 0, order.getId(), UnionType.order, FlowStatus.success);

        final User orderUser = userDao.findById(order.getUserId(), User.class);
        if (null != orderUser && orderUser.getInviteUserId() > 0) {
            final User inviteUser = userDao.findById(orderUser.getInviteUserId(), User.class);
            if (null != inviteUser) {
                final GlobalRule globalRule = globalVariablesService.getGlobalRule();
                if (inviteUser.isSpecial() && globalRule.getProportionOfFriends().doubleValue() > 0) {
                    rewardChange(globalRule.getProportionOfFriends().multiply(BigDecimal.valueOf(totalMoney)).longValue(), inviteUser.getId(), OperateType.dividends, "一级分成", order.getPayType(), 0, order.getId(), UnionType.order, FlowStatus.success);
                }

                if (inviteUser.getInviteUserId() > 0) {
                    final User teamUser = userDao.findById(inviteUser.getInviteUserId(), User.class);
                    if (null != teamUser && teamUser.isSpecial() && globalRule.getProportionOfTeam().doubleValue() > 0) {
                        rewardChange(globalRule.getProportionOfTeam().multiply(BigDecimal.valueOf(totalMoney)).longValue(), teamUser.getId(), OperateType.dividends, "二级分成", order.getPayType(), 0, order.getId(), UnionType.order, FlowStatus.success);
                    }
                }
            }
        }
    }

    private void wealthChange(long offset, long userId, OperateType operateType, String detail, PayType payType, long ip, long unionId, UnionType unionType, FlowStatus flowStatus) throws Exception {
        if (0 == offset) return;
        redisSyncLocker.keyLock(String.format(Global.Constants.REDIS_USER_WEALTH_LOCK, userId), o -> {
            final UserStored user = globalService.getUser(userId);
            if (null == user) {
                logger.error(String.format("正在给未知用户【%s】进行账户变动", userId));
                return null;
            }

            WealthFlow lastFlow = userDao.getLastFlow(userId, WealthType.money);
            long surplus = null == lastFlow ? 0 : lastFlow.getSurplus();
            long total = (null == lastFlow ? 0 : lastFlow.getTotal());
            if (offset > 0 && OperateType.isTrueIncome(operateType)) {
                total += offset;
            }

            final long redisSurplus = globalService.getUserWealthOfMoney(userId, WealthType.money);
            if (surplus != redisSurplus) globalService.removeRedisWealthOfMoney(userId, WealthType.money);

            globalService.addUserWealthOfMoney(userId, WealthType.money, offset);
            globalService.addUserWealthOfMoneyByOperateType(userId, WealthType.money, operateType, offset);
            if (OperateType.isTrueIncome(operateType)) {
                globalService.addUserWealthOfTotalMoney(userId, WealthType.money, offset);
            }

            final long cashSurplus = surplus + offset;
            logger.info(String.format("用户%s上次结余%s元，%s变动%s后剩余%s元", userId, NumberUtil.fen2Yuan(surplus), operateType.getLabel(), NumberUtil.fen2Yuan(offset), NumberUtil.fen2Yuan(cashSurplus)));
            WealthFlow flow = new WealthFlow(userId, offset, cashSurplus, operateType, ip, unionId, unionType, detail);
            flow.setWealthType(WealthType.money);
            flow.setFlowStatus(flowStatus);
            flow.setPayType(payType);
            flow.setTotal(total);
            userDao.saveOrUpdate(flow);


            return null;
        }, null);
    }

    private void rewardChange(long offset, long userId, OperateType operateType, String detail, PayType payType, long ip, long unionId, UnionType unionType, FlowStatus flowStatus) throws Exception {
        if (0 == offset) return;
        redisSyncLocker.keyLock(String.format(Global.Constants.REDIS_USER_WEALTH_LOCK, userId), o -> {
            final UserStored user = globalService.getUser(userId);
            if (null == user) {
                logger.error(String.format("正在给未知用户【%s】进行奖励金账户变动", userId));
                return null;
            }

            WealthFlow lastFlow = userDao.getLastFlow(userId, WealthType.reward);
            long surplus = null == lastFlow ? 0 : lastFlow.getSurplus();
            long total = (null == lastFlow ? 0 : lastFlow.getTotal());
            if (offset > 0) {
                total += offset;
            }

            final long redisSurplus = globalService.getUserWealthOfMoney(userId, WealthType.reward);
            if (surplus != redisSurplus) globalService.removeRedisWealthOfMoney(userId, WealthType.reward);

            globalService.addUserWealthOfMoney(userId, WealthType.reward, offset);
            globalService.addUserWealthOfMoneyByOperateType(userId, WealthType.reward, operateType, offset);
            if (OperateType.isTrueIncome(operateType)) {
                globalService.addUserWealthOfTotalMoney(userId, WealthType.reward, offset);
            }

            final long cashSurplus = surplus + offset;
            logger.info(String.format("用户%s上次奖励金结余%s元，%s变动%s后剩余%s元", userId, NumberUtil.fen2Yuan(surplus), operateType.getLabel(), NumberUtil.fen2Yuan(offset), NumberUtil.fen2Yuan(cashSurplus)));
            WealthFlow flow = new WealthFlow(userId, offset, cashSurplus, operateType, ip, unionId, unionType, detail);
            flow.setWealthType(WealthType.reward);
            flow.setFlowStatus(flowStatus);
            flow.setPayType(payType);
            flow.setTotal(total);
            userDao.saveOrUpdate(flow);

            return null;
        }, null);
    }

    private void scoreChange(long offset, long userId, OperateType operateType, String detail, PayType payType, long ip, long unionId, UnionType unionType, FlowStatus flowStatus) throws Exception {
        if (0 == offset) return;
        redisSyncLocker.keyLock(String.format(Global.Constants.REDIS_USER_WEALTH_LOCK, userId), o -> {
            final UserStored user = globalService.getUser(userId);
            if (null == user) {
                logger.error(String.format("正在给未知用户【%s】进行积分变动", userId));
                return null;
            }

            WealthFlow lastFlow = userDao.getLastFlow(userId, WealthType.score);
            long surplus = null == lastFlow ? 0 : lastFlow.getSurplus();
            long total = (null == lastFlow ? 0 : lastFlow.getTotal());
            if (offset > 0) {
                total += offset;
            }

            final long redisSurplus = globalService.getUserWealthOfMoney(userId, WealthType.score);
            if (surplus != redisSurplus) globalService.removeRedisWealthOfMoney(userId, WealthType.score);

            globalService.addUserWealthOfMoney(userId, WealthType.score, offset);
            globalService.addUserWealthOfMoneyByOperateType(userId, WealthType.score, operateType, offset);
            if (OperateType.isTrueIncome(operateType)) {
                globalService.addUserWealthOfTotalMoney(userId, WealthType.score, offset);
            }

            final long cashSurplus = surplus + offset;
            logger.info(String.format("用户%s上次结余积分%s，%s变动%s后剩余积分%s", userId, surplus, operateType.getLabel(), offset, cashSurplus));
            WealthFlow flow = new WealthFlow(userId, offset, cashSurplus, operateType, ip, unionId, unionType, detail);
            flow.setWealthType(WealthType.score);
            flow.setFlowStatus(flowStatus);
            flow.setPayType(payType);
            flow.setTotal(total);
            userDao.saveOrUpdate(flow);

            return null;
        }, null);
    }
}
